import akka.actor.Actor

class Node(nodeId : String) extends Actor{
    val node = new NodeEntry(nodeId, self.path.toString)
    var routeTable = new RoutingTable(nodeId)
    var leaf = new Leaf(nodeId)
    val supervisor = context.system.actorSelection("akka://System/user/Supervisor")
    val timer = new java.util.Timer()
    var nodes = 0
    var nOfSentMsgs = 0
    var nOfMsgs = 0
    var rnd = new java.util.Random()	
    var routeJumpList = List[Double]()
    
    def receive = {
      case Start(nOfNodes, nOfMessages) => 
        {
          nodes = nOfNodes
          nOfMsgs = nOfMessages
          //println("Node " + self.path.name + " started with ID: " + nodeId)
          if (self.path.name != "1"){
            val knownLink = context.system.actorSelection("akka://System/user/" + (self.path.name.toInt - 1))
            knownLink ! Join(node)
          }
          else{
            supervisor ! StartDone
          }
        }
      case StartSending =>
        {
          startTimer()
        }
      case JoinResponse(respondingNode, tableRow, row, leaf, lastResponce) =>
        {
          //println("Node " + self.path.name + " got JoinResponse message")
          for (n <- tableRow){
            if(n != null){
              routeTable.add(n)
            }
          }
          
          if (lastResponce){
            this.leaf.CopyLeaf(leaf, respondingNode)
            //send to all nodes in leaf
            this.leaf.smaller.foreach(x => context.system.actorSelection(x.getAddress) ! JoinDone(node, routeTable.table, routeTable.lastModified, this.leaf))
            this.leaf.larger.foreach(x => context.system.actorSelection(x.getAddress) ! JoinDone(node, routeTable.table, routeTable.lastModified, this.leaf))
            //send to all nodes in route table
            for (r <- routeTable.table){
              for(n <- r){
                if (n != null){
                  context.system.actorSelection(n.getAddress) ! JoinDone(node, routeTable.table, routeTable.lastModified, this.leaf)
                }
              }
            }
            
            supervisor ! StartDone
          }
        }
      case Join(newNode) =>
        {
          val nextNode = route(newNode.getNodeId)
          //println("Node " + self.path.name + " got Join message")          
          var row = Utils.shl(newNode.getNodeId, node.getNodeId)
          
          if (nextNode != null){
            context.system.actorSelection(nextNode.getAddress) ! Join(newNode)
            context.system.actorSelection(newNode.getAddress) ! JoinResponse(node, routeTable.table(row), row, leaf, false)
          }
          else{
            context.system.actorSelection(newNode.getAddress) ! JoinResponse(node, routeTable.table(row), row, leaf, true)
          }
        }
      case JoinDone(respondingNode, table, tableTime, leaf) =>
        {
          //println("Node " + self.path.name + " got JoinDone message")
          this.leaf.CopyLeaf(leaf, respondingNode)
          
          routeTable.add(respondingNode)
          for (r <- table){
            for (n <- r){
              if (n != null){
            	routeTable.add(n)
              }
            }
          }
          
          context.system.actorSelection(respondingNode.getAddress) ! JoinDoneResponse(node, tableTime, leaf.lastModified)
          
          //leaf.print
          //routeTable.print
        }
      case JoinDoneResponse(respondingNode, tableTime, leafTime) =>
        {
          if (routeTable.lastModified > tableTime || leaf.lastModified > leafTime){
            context.system.actorSelection(respondingNode.getAddress) ! JoinDone(node, routeTable.table, routeTable.lastModified, this.leaf)
          }
        }
      case Route(key, requestor, routeCount) =>
        {
          //if (routeCount == 0)
          //  println("Node " + self.path.name + " got Route message to " + key)
          //leaf.print
          //routeTable.print
          
          if (key.equals(node.getNodeId) || routeCount > 20){
            //println("Sending Route message to requestor " + requestor)
            context.system.actorSelection(requestor) ! RouteResponse(node, routeCount)
          }
          else{
            val nextNode = route(key)
            if (nextNode != null){
              //println("Sending Route message to " + nextNode.getAddress)
              context.system.actorSelection(nextNode.getAddress) ! Route(key, requestor, routeCount+1)
            }
            else{
              //println("Sending Route message to requestor " + requestor)
              context.system.actorSelection(requestor) ! RouteResponse(node, routeCount)
            }
          }
          
        }
      case RouteResponse(node, routeCount) =>
        {
          routeJumpList ++= List(routeCount.toDouble)
          //println("Node " + self.path.name + " got RouteResponse message from " + node.getAddress + " key " + node.getNodeId + " jumps " + routeCount)
          if (routeJumpList.length == nOfMsgs){
            val avg = Utils.calculateAVG(routeJumpList)
            supervisor ! Done(avg)
          }
        }
      case PrintState =>
        {
          leaf.print
          routeTable.print
        }
      case _ =>
        {
          println("Node " + self.path.name + " got unknown message")
        }
    }
    
	def route(routeToId : String) : NodeEntry = {
	  if (leaf.inRange(routeToId)){
	    val to = leaf.getClosestNode(routeToId)
	    if (to == null){
	      //println("I am the node: " + nodeId)
	    }
	    else{
	      //println("send to node from leaf: " + to.getNodeId)  
	    }

	    to
	  }
	  else{
	    val l = Utils.shl(routeToId, nodeId)
	    val sendTo = routeTable.get(l, Utils.getKeyDigitValue(routeToId, l))
	    if (sendTo != null){
	      //println("send to node from routing table: " + sendTo.getNodeId)
	      sendTo
	    }
	    else{
	      val t1 = routeTable.findCloserNode(routeToId, l)
	      if (!t1.isEmpty){
	        //println("send to node from union of all: " + t1.getNodeId)
	        t1
	      }
	      else{
	        val t2 = leaf.getClosestNode(routeToId)
	        if (t2 != null){
	          //println("send to node from union of all: " + t2.getNodeId)
	          sendTo
	        }
	        else{
	          //println("I am the node: " + nodeId)
	          null
	        }
	      }
	    }
	  }
	}
	
	def startTimer(){
	  timer.scheduleAtFixedRate(new java.util.TimerTask() {
        def run(){
          if (nOfSentMsgs == nOfMsgs){
            timer.cancel
          }
          else{
            nOfSentMsgs += 1
            val key = Utils.byteToHex(Utils.hash("akka://System/user/" + rnd.nextInt(nodes))) 
    	    self ! Route(key, node.getAddress, 0)
          }
        }
      }, 1000, 1000);
	}
}